package org.onionframework.faces.extension;

import java.beans.FeatureDescriptor;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import javax.el.ELContext;
import javax.el.ELException;
import javax.faces.model.DataModel;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.jsf.el.SpringBeanFacesELResolver;

public class OnionELResolver extends SpringBeanFacesELResolver {
	
	/** Logger available to subclasses */
	protected final Logger logger = LoggerFactory.getLogger(getClass());


	@Override
	public Object getValue(ELContext elContext, Object base, Object property) throws ELException {
        if (base == null) {
            return resolveBase(elContext, property);
        } else if (base instanceof Namespace) {
            return resolveInNamespace(elContext, (Namespace) base, property);
        } else if (DataModel.class.isInstance(base)) {
            return resolveInDataModel(elContext,  base, property);
            
        } else if (base instanceof Collection) {
            return resolveInCollection(elContext, (Collection<?>) base, property);
            
        } else if (base instanceof Map) {
            return resolveInMap(elContext, (Map<?,?>) base, property);
        }else {
            return null;
        }
	}

	private Object resolveInNamespace(ELContext elContext, Namespace base,
			Object property) {
		Object result = base.get((String) property);
		elContext.setPropertyResolved(true);
        return result;
	}

	private Object resolveBase(ELContext elContext, Object property) {
		if(this.getWebApplicationContext(elContext).containsBean(property.toString())){
			elContext.setPropertyResolved(true);
			return this.getWebApplicationContext(elContext).getBean(property.toString());
		}else{
			String[] aliases = this.getWebApplicationContext(elContext).getAliases(property.toString());
			if(aliases.length > 0){
				elContext.setPropertyResolved(true);
				return this.getWebApplicationContext(elContext).getBean(aliases[0]);
			}else{
				Object result = Namespace.rootNamespace.get(property.toString());
				if(result != null)
					elContext.setPropertyResolved(true);
				
				return result;
			}
		}
	}
	
	   private Object resolveInMap(ELContext context, Map<?,?> map, Object property) {       
	        try {
	            if (map.containsKey(property)) {
	                return null;
	            }
	        } catch (UnsupportedOperationException e) {
	            // eat it
	        }
	        
	        if ("size".equals(property)) {
	            context.setPropertyResolved(true);
	            return map.size();
	            
	        } else if ("values".equals(property)) {
	            context.setPropertyResolved(true);
	            return map.values();
	        
	        } else if ("keySet".equals(property)) {
	            context.setPropertyResolved(true);
	            return map.keySet();
	        
	        } else if ("entrySet".equals(property)) {
	            context.setPropertyResolved(true);
	            return map.entrySet();
	        
	        } else {
	            return null;
	        }
	    }

	   private Object resolveInCollection(ELContext context, Collection<?> collection, Object property)
	   {
	        if ("size".equals(property)) {
	            context.setPropertyResolved(true);
	            return collection.size();
	        } else {
	            return null;
	        }
	    }

	   private Object resolveInDataModel(ELContext context, Object base, Object property)
	   {
	        if ("size".equals(property)) {
	            context.setPropertyResolved(true);
	            DataModel<?> model = (DataModel<?>)base;
	            return model.getRowCount();
	        } else {
	            return null;
	        }
	    }

	@Override
	public Class<?> getType(ELContext elContext, Object base, Object property) throws ELException {
		return super.getType(elContext, base, property);
	}

	@Override
	public void setValue(ELContext elContext, Object base, Object property, Object value) throws ELException {
		super.setValue(elContext, base, property, value);
	}

	@Override
	public boolean isReadOnly(ELContext elContext, Object base, Object property) throws ELException {
		return super.isReadOnly(elContext, base, property);
	}

	@Override
	public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext elContext, Object base) {
		return null;
	}

	@Override
	public Class<?> getCommonPropertyType(ELContext elContext, Object base) {
		return Object.class;
	}
}
